Skip to content

refactor(workflow-compiler): unify duplicate compilers into common/workflow-compiler#6143

Open
Yicong-Huang wants to merge 1 commit into
apache:mainfrom
Yicong-Huang:refactor/unify-workflow-compiler
Open

refactor(workflow-compiler): unify duplicate compilers into common/workflow-compiler#6143
Yicong-Huang wants to merge 1 commit into
apache:mainfrom
Yicong-Huang:refactor/unify-workflow-compiler

Conversation

@Yicong-Huang

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

amber and workflow-compiling-service each maintained a separate copy of the workflow compiler and its models (WorkflowCompiler, LogicalPlan, LogicalLink, LogicalPlanPojo), which had to be kept in sync by hand. This PR consolidates them into a single shared common/workflow-compiler module that both services and amber depend on; the old per-service copies are removed.

The unified WorkflowCompiler.compile returns a WorkflowCompilationResult (logical plan, optional physical plan, per-port output schemas, per-operator errors, and the output ports needing storage) and takes a CompilationErrorHandling mode: Lenient for the editing-time path (accumulate per-operator errors so the UI can render them) and Strict for the execution path (fail-fast before a run). amber's controller Workflow becomes a thin wrapper over the compilation result.

Any related issues, documentation, discussions?

Closes #6142. Design notes live in common/workflow-compiler/DESIGN.md.

How was this PR tested?

Migrated the compiler and model unit specs into the new module and added strict-mode coverage; amber and workflow-compiling-service compile and test against the shared library.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Opus 4.8 (Claude Code)

@github-actions github-actions Bot added engine dependencies Pull requests that update a dependency file refactor Refactor the code ci changes related to CI docs Changes related to documentations common platform Non-amber Scala service paths amber-integration labels Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @aglinxinyuan, @Ma77Ball, @mengw15
    You can notify them by mentioning @aglinxinyuan, @Ma77Ball, @mengw15 in a comment.

@aglinxinyuan aglinxinyuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get input from @bobbai00 and @Xiao-zhen-Liu as well.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates the previously duplicated workflow compiler implementations into a single shared common/workflow-compiler module, repointing amber and workflow-compiling-service to depend on and use the unified API with explicit Lenient (editing-time) vs Strict (execution) error-handling semantics.

Changes:

  • Introduced a shared common/workflow-compiler module exposing WorkflowCompiler.compile(..., CompilationErrorHandling) returning WorkflowCompilationResult (logical plan, optional physical plan, schemas, per-op errors, storage ports).
  • Updated amber execution/adapters and workflow-compiling-service to import and use the shared compiler/models; removed amber’s legacy compiler/model copies.
  • Migrated/expanded compiler unit coverage into the shared module and updated affected amber tests/imports accordingly.

Reviewed changes

Copilot reviewed 35 out of 36 changed files in this pull request and generated no comments.

Show a summary per file
File Description
workflow-compiling-service/src/test/scala/org/apache/texera/service/resource/WorkflowCompilationResourceSpec.scala Updates model/spec references to the shared compiler package.
workflow-compiling-service/src/main/scala/org/apache/texera/service/resource/WorkflowCompilationResource.scala Switches resource to use shared WorkflowCompiler + shared LogicalPlanPojo.
common/workflow-compiler/src/test/scala/org/apache/texera/compiler/WorkflowCompilerSpec.scala Migrates and expands compiler behavior tests (physical plan shape, storage ports, strict mode).
common/workflow-compiler/src/test/scala/org/apache/texera/compiler/model/LogicalLinkSpec.scala Moves test into the unified org.apache.texera.compiler.model package.
common/workflow-compiler/src/test/scala/org/apache/texera/compiler/LogicalPlanSpec.scala Repoints plan tests to unified LogicalPlan/Pojo/Link model types.
common/workflow-compiler/src/main/scala/org/apache/texera/compiler/WorkflowCompiler.scala Implements unified compiler API/result, adds storage-port computation and strict/lenient handling.
common/workflow-compiler/src/main/scala/org/apache/texera/compiler/model/LogicalPlanPojo.scala Moves LogicalPlanPojo into shared compiler model package.
common/workflow-compiler/src/main/scala/org/apache/texera/compiler/model/LogicalPlan.scala Moves LogicalPlan and adds getTerminalOperatorIds used for storage-port selection.
common/workflow-compiler/src/main/scala/org/apache/texera/compiler/model/LogicalLink.scala Moves LogicalLink into shared compiler model package.
common/workflow-compiler/src/main/scala/org/apache/texera/compiler/CompilationErrorHandling.scala Introduces explicit strict vs lenient compilation error-handling mode.
common/workflow-compiler/DESIGN.md Documents rationale, API contract, and module architecture for the unification.
common/workflow-compiler/build.sbt Adds module-local sbt settings/deps for the new shared compiler module.
build.sbt Registers WorkflowCompiler project and rewires amber/workflow-compiling-service dependencies.
amber/src/test/scala/org/apache/texera/workflow/WorkflowCompilerSpec.scala Removes legacy amber compiler spec now covered in the shared module.
amber/src/test/scala/org/apache/texera/web/service/WorkflowExecutionServiceSpec.scala Updates imports to shared LogicalPlanPojo.
amber/src/test/scala/org/apache/texera/amber/engine/faulttolerance/CheckpointSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/e2e/TestUtils.scala Executes compilation in strict mode and adapts to WorkflowCompilationResult for workflow construction.
amber/src/test/scala/org/apache/texera/amber/engine/e2e/ReconfigurationSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/e2e/PauseSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/e2e/DataProcessingSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/e2e/BatchSizePropagationSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/resourcePolicies/ResourcePoliciesSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/ExpansionGreedyScheduleGeneratorSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/DefaultCostEstimatorSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/architecture/scheduling/CostBasedScheduleGeneratorSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/scala/org/apache/texera/amber/engine/architecture/controller/WorkflowSchedulerSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/test/integration/org/apache/texera/amber/engine/e2e/ReconfigurationIntegrationSpec.scala Repoints LogicalLink import to shared compiler model.
amber/src/main/scala/org/apache/texera/workflow/WorkflowCompiler.scala Removes legacy amber compiler implementation (replaced by shared compiler + thin adapter).
amber/src/main/scala/org/apache/texera/workflow/LogicalPlan.scala Removes legacy amber logical-plan model (replaced by shared compiler model).
amber/src/main/scala/org/apache/texera/web/service/WorkflowService.scala Repoints LogicalPlan import to shared compiler model.
amber/src/main/scala/org/apache/texera/web/service/WorkflowExecutionService.scala Adapts execution path to strict compilation + uses shared compilation result and storage ports.
amber/src/main/scala/org/apache/texera/web/resource/SyncExecutionResource.scala Uses strict compilation for validation paths and repoints model/compiler imports.
amber/src/main/scala/org/apache/texera/web/model/websocket/request/WorkflowExecuteRequest.scala Switches request to shared LogicalPlanPojo type (removes inline duplicate model).
amber/src/main/scala/org/apache/texera/web/model/websocket/request/EditingTimeCompilationRequest.scala Repoints LogicalLink/LogicalPlanPojo imports to shared compiler model.
amber/src/main/scala/org/apache/texera/amber/engine/architecture/controller/Workflow.scala Repoints LogicalPlan to shared compiler model in engine workflow wrapper.
.github/workflows/build.yml Adds jacoco aggregation for the new WorkflowCompiler module.
Comments suppressed due to low confidence (1)

common/workflow-compiler/src/main/scala/org/apache/texera/compiler/WorkflowCompiler.scala:262

  • In Strict mode, schema-propagation failures from collectOutputSchemaFromPhysicalPlan are currently written into a throwaway buffer and then ignored (no throw, and they don’t land in operatorIdToError). This violates the Strict "fail-fast" contract and can let execution proceed with schema errors that would only surface later.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Yicong-Huang Yicong-Huang force-pushed the refactor/unify-workflow-compiler branch 2 times, most recently from fb89a9b to 526c9bb Compare July 5, 2026 07:51
@codecov-commenter

codecov-commenter commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.23077% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.76%. Comparing base (92b7fc9) to head (be99b36).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
.../texera/web/service/WorkflowExecutionService.scala 0.00% 8 Missing ⚠️
...ache/texera/common/compiler/WorkflowCompiler.scala 92.00% 0 Missing and 2 partials ⚠️
...he/texera/web/resource/SyncExecutionResource.scala 0.00% 1 Missing ⚠️
...era/common/compiler/CompilationErrorHandling.scala 66.66% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6143      +/-   ##
============================================
- Coverage     59.80%   59.76%   -0.05%     
+ Complexity     3295     3283      -12     
============================================
  Files          1133     1131       -2     
  Lines         43801    43777      -24     
  Branches       4748     4749       +1     
============================================
- Hits          26197    26163      -34     
- Misses        16174    16186      +12     
+ Partials       1430     1428       -2     
Flag Coverage Δ
access-control-service 70.00% <ø> (ø)
agent-service 44.59% <ø> (ø)
amber 64.76% <69.23%> (-0.03%) ⬇️
computing-unit-managing-service 0.00% <ø> (ø)
config-service 52.30% <ø> (ø)
file-service 62.81% <ø> (ø)
frontend 51.45% <ø> (-0.04%) ⬇️
notebook-migration-service 78.57% <ø> (ø)
pyamber 91.19% <ø> (ø)
workflow-compiling-service 26.31% <ø> (-28.84%) ⬇️

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 0 better · 🔴 10 worse · ⚪ 5 noise (<±5%) · 0 without baseline

Compared against main 44ec60d benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 362 0.221 26,790/37,081/37,081 us 🔴 +23.7% / 🔴 +144.9%
🔴 bs=100 sw=10 sl=64 777 0.474 126,929/158,025/158,025 us 🔴 +20.6% / 🔴 +46.4%
bs=1000 sw=10 sl=64 916 0.559 1,092,689/1,148,923/1,148,923 us ⚪ within ±5% / 🔴 +11.7%
Baseline details

Latest main 44ec60d from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 362 tuples/sec 438 tuples/sec 776.36 tuples/sec -17.4% -53.4%
bs=10 sw=10 sl=64 MB/s 0.221 MB/s 0.267 MB/s 0.474 MB/s -17.2% -53.4%
bs=10 sw=10 sl=64 p50 26,790 us 21,657 us 12,636 us +23.7% +112.0%
bs=10 sw=10 sl=64 p95 37,081 us 33,173 us 15,143 us +11.8% +144.9%
bs=10 sw=10 sl=64 p99 37,081 us 33,173 us 18,954 us +11.8% +95.6%
bs=100 sw=10 sl=64 throughput 777 tuples/sec 834 tuples/sec 985.33 tuples/sec -6.8% -21.1%
bs=100 sw=10 sl=64 MB/s 0.474 MB/s 0.509 MB/s 0.601 MB/s -6.9% -21.2%
bs=100 sw=10 sl=64 p50 126,929 us 120,681 us 101,671 us +5.2% +24.8%
bs=100 sw=10 sl=64 p95 158,025 us 131,040 us 107,939 us +20.6% +46.4%
bs=100 sw=10 sl=64 p99 158,025 us 131,040 us 113,798 us +20.6% +38.9%
bs=1000 sw=10 sl=64 throughput 916 tuples/sec 929 tuples/sec 1,016 tuples/sec -1.4% -9.9%
bs=1000 sw=10 sl=64 MB/s 0.559 MB/s 0.567 MB/s 0.62 MB/s -1.4% -9.9%
bs=1000 sw=10 sl=64 p50 1,092,689 us 1,082,805 us 989,693 us +0.9% +10.4%
bs=1000 sw=10 sl=64 p95 1,148,923 us 1,095,793 us 1,028,327 us +4.8% +11.7%
bs=1000 sw=10 sl=64 p99 1,148,923 us 1,095,793 us 1,059,969 us +4.8% +8.4%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,552.10,200,128000,362,0.221,26790.24,37080.74,37080.74
1,100,10,64,20,2575.27,2000,1280000,777,0.474,126928.97,158025.16,158025.16
2,1000,10,64,20,21829.46,20000,12800000,916,0.559,1092689.32,1148922.74,1148922.74

@Yicong-Huang Yicong-Huang force-pushed the refactor/unify-workflow-compiler branch from 526c9bb to dd92287 Compare July 5, 2026 08:09
…rkflow-compiler

Merge amber's and workflow-compiling-service's separate WorkflowCompiler implementations into a shared common/workflow-compiler module exposing one compile API with Lenient (editing-time, accumulate per-op errors) and Strict (execution, fail-fast) modes. amber's controller Workflow becomes a thin wrapper over the compilation result. Migrate the compiler/model unit specs into the module and add WorkflowCompiler/jacoco to the amber Codecov job so the new module reports coverage.
@Yicong-Huang Yicong-Huang force-pushed the refactor/unify-workflow-compiler branch from dd92287 to be99b36 Compare July 5, 2026 08:13

@bobbai00 bobbai00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Left few minor comments

@@ -0,0 +1,61 @@
# Design: Unified `WorkflowCompiler`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this one be removed ?

)
} catch {
case err: Throwable =>
errorHandler(err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we directly return to end this function here ?

{
val externalLinks = logicalPlan
.getUpstreamLinks(logicalOp.operatorIdentifier)
.filter(link => physicalOp.inputPorts.contains(link.toPortId))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we save the upstream links as a variable outside of this loop ?

@chenlica

chenlica commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Please chime in.

@Yicong-Huang A general question: should we create a discussion first? Or in what cases should we create a discussion before raising a PR?

@Yicong-Huang

Yicong-Huang commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@Xiao-zhen-Liu Please chime in.

@Yicong-Huang A general question: should we create a discussion first? Or in what cases should we create a discussion before raising a PR?

We create discussions when there could have different opinions, for instance when we are unsure if support a feature or not, when we are not sure whether support something or not, or whether we want to remove/disable something. The discussion usually is on the direction.

For this particular case, I feel having two copies of the compiler source code is problematic and adds maintenance cost. Thus, I did not ask for input to unifying them by creating a shared lib.

There is no change on the design, or experience: we still do two compilations, one at editing time, through compilation service, one at execution time, through execution service. It's only a code level refactoring.

@Xiao-zhen-Liu Xiao-zhen-Liu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor — merging the two hand-synced compiler copies into one module is a clear win, and the boundary is clean (common/workflow-compiler depends only on WorkflowOperator; the WorkflowCompilationResult / CompilationErrorHandling split reads well).

One ask: could you update the PR description to make clear this is source-level de-duplication, not a change to how many times a workflow compiles? We still compile twice at runtime (editing + execution). As written it reads like it might be collapsing them, which threw me off — a line like "two compilations stays; this only shares the code" would clear it up.

I checked both callers against their old versions — faithful almost everywhere. A few non-blocking things inline: Strict doesn't actually fail fast on schema errors (drops them) though the docs say otherwise; one real behavior change on the editing path (Try vs the old catch Throwable); the failed-compile path falls through to a null workflow (bobbai00 spotted it); and Strict is thinly tested. One more small one: the compiling-service caller relies on the default Lenient while amber passes Strict explicitly — passing it explicitly there too would read symmetrically.

Also +1 to bobbai00 on hoisting getUpstreamLinks and the DESIGN.md question (no other module ships one). Good to merge once the Strict/schema wording is sorted or a follow-up is filed.

// even if error is encountered when logical => physical, we still want to get the input schemas
// for the rest of the no-error operators. In Strict mode there is no buffer (errors already thrown),
// so schema errors go to a throwaway buffer and do not affect the result.
val schemaErrorList = errorList.getOrElse(new ArrayBuffer[(OperatorIdentity, Throwable)]())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict sets errorList = None, so this buffer is never read and hasErrors (line 263) is always false. Schema-propagation errors (e.g. a Projection on a missing column) get dropped, physicalPlan stays Some, and the run proceeds. Matches old amber (not a regression) but contradicts the fail-fast claim above — and such a plan can still be launched from the UI. Fix the wording, or throw when the buffer is non-empty (a behavior change → probably a follow-up).

val logicalOp = logicalPlan.getOperator(logicalOpId)
val allUpstreamLinks = logicalPlan.getUpstreamLinks(logicalOp.operatorIdentifier)
logicalPlan.getTopologicalOpIds.asScala.foreach(logicalOpId =>
Try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change from the merge: the old compiling-service used catch { case e: Throwable }; scala.util.Try catches only non-fatal errors. So a fatal error during expansion now fails the /compile request instead of being recorded per-operator. Probably fine — flagging since the PR says nothing changes.

*
* - [[CompilationErrorHandling.Lenient]] collects every error and returns them in the result,
* with `physicalPlan = None` when any error occurred. Used for editing-time validation.
* - [[CompilationErrorHandling.Strict]] throws on the first error. Used before execution.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small accuracy fix: Strict doesn't throw on schema-propagation errors — they become Left and are never thrown (see WorkflowCompiler.scala:260).

.filter(op => jgraphtDag.outDegreeOf(op) == 0)
.toList

def addOperator(op: LogicalOp): LogicalPlan = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale TODO, and addOperator/addLink came over from the old code with no callers I could find — drop them or note why they stay.

// -------------------- strict-mode error semantics --------------------

// Re-anchor the subject after the sub-section.
"WorkflowCompiler in strict mode" should "throw when a scan source has no fileName set" in {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one Strict test. Worth adding a Strict success case (the execution path relies on physicalPlan.get) and a schema-error case to pin the behavior. The Python code-gen error branch (~line 179) is untested in both modes too.

)
} catch {
case err: Throwable =>
errorHandler(err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to bobbai00's point here: if compile throws, this reports the error but execution then falls through to workflow.context below with workflow still null → a second NPE that masks the real message. A return after errorHandler(err) fixes it. Pre-existing, but a good spot to fix now that Strict is the fail-fast path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amber-integration ci changes related to CI common dependencies Pull requests that update a dependency file docs Changes related to documentations engine platform Non-amber Scala service paths refactor Refactor the code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify the duplicate workflow compilers into a shared common module

7 participants